package com.tqyan.start.controller;

import com.alibaba.fastjson.JSONObject;
import com.tqyan.model.Result;
import com.tqyan.model.controller.request.RechargeRequest;
import com.tqyan.model.dto.UserDTO;
import com.tqyan.service.service.AccountService;
import com.tqyan.service.service.UserService;
import com.tqyan.service.transfer.LimitService;
import com.tqyan.service.transfer.jieba.PayUtil;
import com.tqyan.start.cfg.WechatConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * TODO
 *
 * @author yinghao
 * @version V1.0.0
 * @date 2023/5/11 15:32
 * @description : 简述类的作用
 */
@RestController
@RequestMapping("/wx")
@Slf4j
public class PayController {
    private static final String CALLBACK_URL="/wx/payment-notify";

    @Autowired
    private UserService userService;
    @Autowired
    private LimitService limitService;
    @Autowired
    private AccountService accountService;

    @RequestMapping(value = "/wxPay", method = RequestMethod.POST)
    public Result wxPay(@RequestBody RechargeRequest rechargeRequest, HttpServletRequest request) {
        try {
            //生成的随机字符串
            String nonce_str = getRandomStringByLength(32);
            //商品名称
            String body = "堂前燕充值";
            //获取客户端的ip地址
            String spbill_create_ip = getIpAddr(request);

            //组装参数，用户生成统一下单接口的签名
            Map<String, String> packageParams = new HashMap<>();
            packageParams.put("appid", WechatConfig.appid);
            packageParams.put("mch_id", WechatConfig.mch_id);
            packageParams.put("nonce_str", nonce_str);
            packageParams.put("body", body);
            packageParams.put("out_trade_no", "recharge-" + System.currentTimeMillis());//商户订单号,自己的订单ID
            packageParams.put("total_fee", rechargeRequest.getTotalFee()+"");//支付金额，这边需要转成字符串类型，否则后面的签名会失败
            packageParams.put("spbill_create_ip", spbill_create_ip);
            String domain = rechargeRequest.getDomain();
            packageParams.put("notify_url", domain + CALLBACK_URL);//支付成功后的回调地址
            packageParams.put("trade_type", WechatConfig.TRADETYPE);//支付方式
            packageParams.put("openid", rechargeRequest.getOpenId() + "");//用户的openID，自己获取

            String prestr = PayUtil.createLinkString(packageParams); // 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
            log.info("prestr="+prestr);
            //MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
            String mysign = PayUtil.sign(prestr, WechatConfig.key, "utf-8").toUpperCase();
            log.info("mysign="+mysign);
            //拼接统一下单接口使用的xml数据，要将上一步生成的签名一起拼接进去
            String xml = "<xml>" + "<appid>" + WechatConfig.appid + "</appid>"
                    + "<body><![CDATA[" + body + "]]></body>"
                    + "<mch_id>" + WechatConfig.mch_id + "</mch_id>"
                    + "<nonce_str>" + nonce_str + "</nonce_str>"
                    + "<notify_url>" + domain + CALLBACK_URL + "</notify_url>"
                    + "<openid>" + rechargeRequest.getOpenId() + "</openid>"
                    + "<out_trade_no>" + packageParams.get("out_trade_no") + "</out_trade_no>"
                    + "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>"
                    + "<total_fee>" + rechargeRequest.getTotalFee() + "</total_fee>"//支付的金额，单位：分
                    + "<trade_type>" + WechatConfig.TRADETYPE + "</trade_type>"
                    + "<sign>" + mysign + "</sign>"
                    + "</xml>";
            log.info("xml="+xml);
            //调用统一下单接口，并接受返回的结果
            String result = PayUtil.httpRequest(WechatConfig.pay_url, "POST", xml);
            log.info("result="+result);
            // 将解析结果存储在HashMap中
            Map map = PayUtil.doXMLParse(result);

            String return_code = (String) map.get("return_code");//返回状态码
            String result_code = (String) map.get("result_code");//返回状态码

            log.info("return_code="+return_code);
            log.info("result_code="+result_code);
            Map<String, Object> response = new HashMap<String, Object>();//返回给小程序端需要的参数
            log.info("return_code.equals(result_code)="+("SUCCESS".equals(return_code) && return_code.equals(result_code)));
            if ("SUCCESS".equals(return_code) && return_code.equals(result_code)) {
                log.info("paySign="+result_code);
                String prepay_id = (String) map.get("prepay_id");//返回的预付单信息
                response.put("nonceStr", nonce_str);
                response.put("package", "prepay_id=" + prepay_id);
                Long timeStamp = System.currentTimeMillis() / 1000;
                response.put("timeStamp", timeStamp + "");//这边要将返回的时间戳转化成字符串，不然小程序端调用wx.requestPayment方法会报签名错误
                //拼接签名需要的参数
                String stringSignTemp = "appId=" + WechatConfig.appid + "&nonceStr=" + nonce_str + "&package=prepay_id=" + prepay_id + "&signType=MD5&timeStamp=" + timeStamp;
                //再次签名，这个签名用于小程序端调用wx.requesetPayment方法
                String paySign = PayUtil.sign(stringSignTemp, WechatConfig.key, "utf-8").toUpperCase();
                log.info("paySign="+paySign);
                response.put("paySign", paySign);
            }

            response.put("appid", WechatConfig.appid);
            log.info("response="+ JSONObject.toJSONString(response));
            return Result.success(response);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return  Result.fail("充值失败");
    }

    /**
     * @description 微信支付回调接口，微信支付成功后会自动调用
     * <p> <功能详细描述> </p>
     *
     * @author 英浩
     * @date 2023/5/18 12:13
     */
    @RequestMapping(value = "/payment-notify", method = RequestMethod.POST)
    public void wxNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        log.info("[微信支付] 支付回调【开始】");
        // 读取回调内容
        BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
        String line;
        StringBuilder sb = new StringBuilder();
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        br.close();
        String notifyXML = sb.toString();
        Map notifyMap = PayUtil.doXMLParse(notifyXML);
        log.info("[微信支付] notifyXML={}, notifyMap={}, 读取回调内容", notifyXML, notifyMap);
        // 支付回调接口map={"transaction_id":"4200001859202305113131290073","nonce_str":"2ulegljk9m8e59akkndx2gr3rzy9sghq",
        // "bank_type":"OTHERS","openid":"ociQe5XGxHcLfLJ2kLId6cFDXbLI","sign":"78629B84ED661694451A00CFF6932B96",
        // "fee_type":"CNY","mch_id":"1547441881","cash_fee":"1","out_trade_no":"recharge-1683800578408",
        // "appid":"wxaf00cbb6ccc9caf1","total_fee":"1","trade_type":"JSAPI","result_code":"SUCCESS",
        // "time_end":"20230511182309","is_subscribe":"N","return_code":"SUCCESS"}

        // 提取回调参数
        String openid = (String) notifyMap.get("openid");
        int totalFee = Integer.parseInt((String) notifyMap.get("total_fee"));  // 分
        String transactionId = (String) notifyMap.get("transaction_id");
        String outTradeNo = (String) notifyMap.get("out_trade_no");
        String returnCode = (String) notifyMap.get("return_code");

        // 已支付
        if (limitService.isPay(transactionId)) {
            log.error("[微信支付] openid={}, transactionId={}, outTradeNo={}, 已支付"
                    , openid, transactionId, outTradeNo);
            this.response(response, true, null);
            return;
        }
        // 添加处理锁
        String key = openid + ":" + transactionId;
        if (!limitService.lock(key)) {
            log.error("[微信支付] openid={}, transactionId={}, outTradeNo={}, 支付处理中【幂等】"
                    , openid, transactionId, outTradeNo);
            this.response(response, false, "支付处理中");
            return;
        }

        try {
            // 查询用户信息
            UserDTO user = userService.getUserInfo(openid);
            if (null == user) {
                log.error("[微信支付] openid={}, transactionId={}, outTradeNo={}, 用户信息不存在"
                        , openid, transactionId, outTradeNo);
                this.response(response, false, "支付处理失败");
                return;
            }

            if ("SUCCESS".equals(returnCode)) {
                if (totalFee > 0) {
                    // 用户充值
                    accountService.recharge(user.getId(), totalFee, JSONObject.toJSONString(notifyMap));
                    // 标记支付完成
                    limitService.setIsPay(transactionId);
                    log.info("[微信支付] openid={}, transactionId={}, outTradeNo={}, totalFee={}, 充值完成"
                            , openid, transactionId, outTradeNo, totalFee);
                }
                // 通知微信服务器已经支付成功
                this.response(response, true, null);
            } else {
                log.error("[微信支付] openid={}, transactionId={}, outTradeNo={}, returnCode={}, returnCode非成功"
                        , openid, transactionId, outTradeNo, returnCode);
                this.response(response, false, "returnCode非成功");
            }
        } catch (Exception e) {
            log.error("[微信支付] notifyMap={}, 支付处理异常: {}", notifyMap, e.getMessage(), e);
            this.response(response, false, "支付处理失败");
        } finally {
            // 释放处理锁
            limitService.unlock(key);
        }
    }

    /**
     * @description 响应微信
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/5/18 11:52
     */
    private void response(HttpServletResponse response, boolean isSuccess, String errMsg) throws IOException {
        String result = "<xml>" +
                "<return_code><![CDATA[" + (isSuccess ? "SUCCESS" : "FAIL") + "]]></return_code>" +
                "<return_msg><![CDATA[" + (isSuccess ? "OK" : errMsg) + "]]></return_msg>" +
                "</xml> ";
        BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
        out.write(result.getBytes());
        out.flush();
        out.close();
    }

    //获取随机字符串
    private String getRandomStringByLength(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    //获取IP
    private String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            //多次反向代理后会有多个ip值，第一个ip才是真实ip
            int index = ip.indexOf(",");
            if (index != -1) {
                return ip.substring(0, index);
            } else {
                return ip;
            }
        }
        ip = request.getHeader("X-Real-IP");
        if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            return ip;
        }
        return request.getRemoteAddr();
    }
}
